package org.moonlight.jvm.rtda.share.heap.classmember;

import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.moonlight.jvm.classfile.MemberInfo;
import org.moonlight.jvm.classfile.attribute.CodeAttribute;
import org.moonlight.jvm.classfile.attribute.debuginfo.LineNumberTableAttribute;
import org.moonlight.jvm.rtda.share.heap.Class;
import org.moonlight.jvm.rtda.share.heap.exception.ExceptionTable;
import org.moonlight.jvm.utils.MethodDescriptor;

import java.util.List;
import java.util.Objects;

/**
 * 方法信息类 - 存储 Class 中的方法信息
 *
 * @author Moonlight
 * @createTime 2023/8/29 16:43
 **/
@Getter
@EqualsAndHashCode(callSuper = true)
public class Method extends ClassMember {

    /** 操作数栈大小 **/
    private int maxStack;
    /** 局部变量表大小 **/
    private int maxLocals;
    /** 字节码 **/
    private byte[] code;
    /** 方法参数占用多少个插槽  **/
    private int argSlotCount;
    /** 参数列表 **/
    private List<String> parameterTypes;
    /** 返回值 **/
    private String returnType;
    /** 异常处理表 **/
    private ExceptionTable exceptionTable;
    /** 行号信息 **/
    private LineNumberTableAttribute lineNumberTable;

    public void copyAttributes(MemberInfo memberInfo) {
        CodeAttribute codeAttribute = memberInfo.codeAttribute();
        if (codeAttribute != null) {
            this.maxStack = codeAttribute.getMaxStack();
            this.maxLocals = codeAttribute.getMaxLocals();
            this.code = codeAttribute.getCode();
            this.exceptionTable = new ExceptionTable(codeAttribute.getExceptionTable(), this.clazz.getRtCp());
            this.lineNumberTable = codeAttribute.lineNumberTableAttribute();
        }
    }

    /**
     * 解析方法描述符
     *
     * @createTime 15:00 2023/9/20
     * @author moonlight
     **/
    private void parseMethodDescriptor() {
        MethodDescriptor methodDescriptor = MethodDescriptor.methodDescriptor(this.descriptor);

        this.parameterTypes = methodDescriptor.getParameterTypes();
        this.returnType = methodDescriptor.getReturnType();
    }

    /**
     * 计算 方法参数 占用多少个位置
     * 如果 parameterTypes 为 null 或者 size 为 0 会先调用 parseMethodDescriptor 对 方法描述符 进行解析
     *
     * @createTime 15:01 2023/9/20
     * @author moonlight
     **/
    private void calcArgSlotCount() {
        if (this.parameterTypes == null || this.parameterTypes.size() == 0) {
            parseMethodDescriptor();
        }
        for (String param : this.parameterTypes) {
            this.argSlotCount++;
            // Long 和 Double 要占用两个位置 - 但是在本工程的实际实现中，并没有给 Long 或 Double 两个位置
//            if (Objects.equals(param, "J") || Objects.equals(param, "D")) {
//                this.argSlotCount++;
//            }
        }
        // this 引用
        if (!this.isStatic()) {
            this.argSlotCount++;
        }
    }

    public static Method[] newMethods(Class clazz, MemberInfo[] classFileMemberInfo) {
        Method[] methods = new Method[classFileMemberInfo.length];
        for (int i = 0; i < classFileMemberInfo.length; i++) {
            methods[i] = newMethod(classFileMemberInfo[i], clazz);
        }
        return methods;
    }

    public static Method newMethod(MemberInfo memberInfo, Class clazz) {
        Method method = new Method();
        method.clazz = clazz;
        method.copyMemberInfo(memberInfo);
        method.copyAttributes(memberInfo);
        method.parseMethodDescriptor();
        method.calcArgSlotCount();
        // 如果是本地方法, 则注入字节码和其他代码信息
        if (method.isNative()) {
            method.injectCodeAttribute();
        }
        return method;
    }

    /**
     * 注入字节码和其他代码信息
     *
     * Java虚拟机规范并没有规定如何实现和调用本地方法，我们可以利用Java虚拟机栈执行本地方法。
     * 虽然，本地方法并没有字节码。但是，Java虚拟机规范预留了两条指令，操作码分别是 0xFE 和 0xFF。可以使用 0xFE 指令来达到这个目的。
     *
     * 本地方法在class文件中没有Code属性，所以需要给maxStack和maxLocals字段赋值。
     * 本地方法帧的操作数栈至少要能容纳返回值，为了简化代码，暂时给maxStack字段赋值为4。
     * 因为本地方法帧的局部变量表只用来存放参数值，所以把argSlotCount赋给maxLocals字段刚好。
     * 至于code字段，也就是本地方法的字节码，第一条指令都是0xFE，第二条指令则根据函数的返回值选择相应的返回指令。
     *
     * @createTime 15:10 2023/9/20
     * @author moonlight
     **/
    private void injectCodeAttribute() {
        this.maxStack = 4;
        this.maxLocals = this.argSlotCount;
        switch (this.returnType.charAt(0)) {
            case 'V':
                // return
                this.code = new byte[]{ (byte) 0xfe, (byte) 0xb1 };
                return;
            case 'L':
            case '[':
                // areturn
                this.code = new byte[]{ (byte) 0xfe, (byte) 0xb0 };
                return;
            case 'D':
                // dreturn
                this.code = new byte[]{ (byte) 0xfe, (byte) 0xaf };
                return;
            case 'F':
                // freturn
                this.code = new byte[]{ (byte) 0xfe, (byte) 0xae };
                return;
            case 'J':
                // lreturn
                this.code = new byte[]{ (byte) 0xfe, (byte) 0xad };
                return;
            default:
                // ireturn
                this.code = new byte[]{ (byte) 0xfe, (byte) 0xac };
        }
    }

    public boolean isAbstract() {
        return (this.accessFlags & AccessFlags.ACC_ABSTRACT) != 0;
    }

    public boolean isNative() {
        return (this.accessFlags & AccessFlags.ACC_NATIVE) != 0;
    }

    public int findExceptionHandler(Class exClass, int pc) {
        // 搜索异常处理表，如果能够找到对应的异常处理项，则返回它的handlerPc字段，否则返回-1
        ExceptionTable.ExceptionHandler handler = this.exceptionTable.findExceptionHandler(exClass, pc);
        if (handler != null) {
            return handler.getHandlerPc();
        }
        return -1;
    }

    public int getLineNumber(int pc) {
        // 并不是每个方法都有行号表。如果方法没有行号表，自然也就查不到pc对应的行号，这种情况下返回-1。本地方法没有字节码，这种情况下返回-2
        if (this.isNative()) {
            return -2;
        }
        if (this.lineNumberTable == null) {
            return -1;
        }
        return this.lineNumberTable.getLineNumber(pc);
    }

}